﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace M3G {
    public class CompositingMode : Object3D {

        #region Constant

        // BlendingMode
        public const int 	Alpha              =  64;
        public const int 	AlphaAdd	       =  65;
        public const int 	Modulate	       =  66;
        public const int 	ModulateX2	       =  67;
        public const int 	Replace	           =  68;
        public const int 	Add	               =  69;
        public const int 	AlphaDarken	       =  70;
        public const int 	AlphaPremultiplied =  71;
        public const int 	ModulateInv	       =  72;

        // Alpha/DepthTestFunction
        public const int 	Never	           = 512;
        public const int 	Less	           = 513;
        public const int 	Equal	           = 514;
        public const int 	LEqual             = 515;
        public const int 	Greater	           = 516;
        public const int 	NotEqual	       = 517;
        public const int 	GEqual             = 518;
        public const int 	Always	           = 519;
        #endregion

        #region Field
        int     blendingMode;
        float   alphaThreshold;
        int     alphaTestFunction;
        float   depthOffsetFactor;
        float   depthOffsetUnit;
        bool    depthTestEnabled;
        int     depthTestFunction;
        bool    depthWriteEnabled;
        uint    colorWriteMask;
        Blender blender;
        Stencil stencil;
        #endregion

        #region Method
        public CompositingMode () {
            this.blendingMode = CompositingMode.Replace;
            this.alphaThreshold = 0.0f;
            this.alphaTestFunction = CompositingMode.GEqual;
            this.depthOffsetFactor = 0.0f;
            this.depthOffsetUnit = 0.0f;
            this.depthTestEnabled = true;
            this.depthTestFunction = CompositingMode.LEqual;
            this.depthWriteEnabled = true;
            this.colorWriteMask = 0xffffffffu;
            this.blender = null;
            this.stencil = null;
        }

        protected CompositingMode (CompositingMode compositingMode)
            : base (compositingMode) {
            this.blendingMode = compositingMode.blendingMode;
            this.alphaThreshold = compositingMode.alphaThreshold;
            this.alphaTestFunction = compositingMode.alphaTestFunction;
            this.depthOffsetFactor = compositingMode.depthOffsetFactor;
            this.depthOffsetUnit = compositingMode.depthOffsetUnit;
            this.depthTestEnabled = compositingMode.depthTestEnabled;
            this.depthTestFunction = compositingMode.depthTestFunction;
            this.depthWriteEnabled = compositingMode.depthWriteEnabled;
            this.colorWriteMask = compositingMode.colorWriteMask;
            this.blender = compositingMode.blender;
            this.stencil = compositingMode.stencil;
        }


        public bool DepthTestEnabled {
            get { return depthTestEnabled; }
            set { depthTestEnabled = value; }
        }

        public bool DepthWriteEnabled {
            get { return depthWriteEnabled; }
            set { depthWriteEnabled = value; }
        }



        public int DepthTest {
            get { return depthTestFunction; }
            set {
                var func = value;
                if (func < CompositingMode.Less || func > CompositingMode.Always) {
                    throw new ArgumentException ("Depth test function must be Never,Always,Less,LEqual,Equal,GEqual,Greater,NotEqual");
                }

                this.depthTestFunction = func;
            }
        }

        public int AlphaTest {
            get {
                return alphaTestFunction;
            }
            set {
                var func = value;
                if (func < CompositingMode.Less || func > CompositingMode.Always) {
                    throw new ArgumentException ("Alpha test function must be Never, Always, Less, LEqual, Equal, GEqual, Greater, NotEqual, func=" + func);
                }

                this.alphaTestFunction = func;
            }
        }

        public float AlphaThreshold {
            get {
                return alphaThreshold;
            }
            set {
                var threshold = value;
                if (threshold < 0 || threshold > 1) {
                    throw new ArgumentException ("Alpha threshold must be in [0,1], th=" + threshold);
                }

                this.alphaThreshold = threshold;
            }
        }


        public int Blending {
            get {
                return blendingMode;
            }
            set {
                var blending = value;
                if (blending < CompositingMode.Alpha || blending > CompositingMode.ModulateInv) {
                    throw new ArgumentException ("Blending must be Replace,Add,Alpha,AlphaAdd,AlphaDarken,AlphaPremultiplied,Modulat,ModulateX2,ModulateInv, blendig=" + blending);
                }

                this.blendingMode = blending;
            }
        }

        public uint ColorWriteMask {
            get {
                return colorWriteMask;
            }
            set {
                var mask = value;
                var a = (mask & 0xff000000) >> 24;
                var r = (mask & 0x00ff0000) >> 16;
                var g = (mask & 0x0000ff00) >> 8;
                var b = (mask & 0x000000ff) >> 0;
                if (!(a == 0 || a == 0xff) || !(r == 0 || r == 0xff) || !(g == 0 || g == 0xff) || !(b == 0 || b == 0xff)) {
                    throw new ArgumentException ("All components of mask must be 0x00 or 0xff, mask=" + mask);
                }

                this.colorWriteMask = mask;
            }
        }

        public float DepthOffsetFactor {
            get { return depthOffsetFactor; }
        }

        public float DepthOffsetUnits {
            get { return depthOffsetUnit; }
        }

        public void SetDepthOffset (float factor, float units) {
            this.depthOffsetFactor = factor;
            this.depthOffsetUnit = units;
        }

        public Blender Blender {
            get {
                return blender;
            }
        }

        public void SetBlender (Blender blender) {
            this.blender = blender;
        }

        public Stencil Stencil {
            get {
                return stencil;
            }
        }

        public void SetStencil (Stencil stencil) {
            this.stencil = stencil;
        }

        public override Object3D[] References {
            get {
                var q = new List<Object3D> ();
                q.AddRange (base.References);
                q.Add (blender);
                q.Add (stencil);
                q.RemoveAll (x => x == null);
                return q.ToArray ();
            }
        }

        public override Object3D Duplicate () {
            return new CompositingMode (this);
        }

        #endregion

    }
}
